package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;

/**
 * 聊天室服务端
 */
public class Server {
    /*
        java.net.ServerSocket
        运行在服务端的ServerSocket主要工作:
        1:向系统申请对外的服务端口，客户端就是通过这个端口与服务端建立连接的
        2:监听服务端口，一旦一个客户端建立连接立即接受并获取一个Socket实例与之交互

        将ServerSocket想象为某客服呼叫中心的"总机"。用户拨打电话总是打给总机，然后
        总机下面连接着若干部电话，分配一台电话与该用户沟通，从而做到服务端可以同时与
        多个用户沟通的效果。
     */
    private ServerSocket serverSocket;

    //存放对应所有客户端的输出流，用于广播消息
    //    private PrintWriter[] allOut = {};
    private Collection<PrintWriter> allOut = new ArrayList<>();

    public Server() {
        try {
            System.out.println("正在启动服务端...");
             /*
                ServerSocket在创建时需要指定对外的服务端口，该端口不能不能与服务器
                上其他运行的应用程序申请的端口一致，否则会报错:
                java.net.BindException:address already in use
                告知该地址已经被占用了，此时需要更换端口，或者自行在操作系统中杀死
                占用该端口的应用程序。
             */
            serverSocket = new ServerSocket(8099);
            System.out.println("服务器启动完毕！");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 该线程任务负责与指定的客户端进行交互
     */
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host;//记录客户端的IP地址


        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket获取远端计算机(客户端)的IP地址信息
            host = socket.getInetAddress().getHostAddress();
        }

        @Override
        public void run() {
            PrintWriter pw = null;
            try {
                /*
                    Socket重要的方法:
                    InputStream getInputStream()
                    通过Socket获取的输入流可以读取远端计算机发送过来的数据
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);

                //通过socket获取输出流用于将消息发送给该客户端
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw, true);

//              synchronized (this) {//不同线程调用不同的ClientHandler的run方法,因此run方法中的this(ClientHandler不是同一个)
//              synchronized (allOut) {//线程进入到同步块中会创建新的数组对象赋值给allOut,导致其如果作为锁对象会发生改变.

//              synchronized (Server.this) {//如果是数组,则可以锁该对象
//              allOut = Arrays.copyOf(allOut, allOut.length + 1);
//              allOut[allOut.length - 1] = pw;
//              sendMessage(host + "上线了，当前在线人数：" + allOut.length);
//              }

                //将该输出流存入到共享的集合中，以便其他ClientHandler可以将消息发送给这个客户端
                synchronized (allOut) {
                    allOut.add(pw);
                }
                //广播该客户端上线的信息
                sendMessage(host + "上线了，当前在线人数:" + allOut.size());

                //读取来自客户端发送过来的一行字符串
                String line;
                /*
                    当客户端异常断开连接(如果客户端没有调用socket.close()断开)
                    windows的客户端如果异常断开:服务端这里通常readLine方法会直接抛出
                    异常:java.net.SocketException: Connection reset。

                    linux的客户端异常断开:服务端这里readLine通常会返回null。
                 */
                while ((line = br.readLine()) != null) {

                    if (line.contains("神经病")) {//屏蔽词
                        line = line.replace("神经病", "*");
                    }

                    sendMessage(host + "说：" + line);

                }
            } catch (IOException e) {//断线报错处理机制
//                sendMessage(host + "断线了，当前在线人数：" + allOut.length);
                sendMessage(host + "断线了，当前在线人数：" + allOut.size());
            } finally {
                //处理客户端断开连接
                //将pw从数组allOut中删除 直接将数组最后一个元素 赋值给pw 然后缩容可以吗，
//                synchronized (Server.this) {
//                for (int i = 0; i < allOut.length; i++) {
//                    if (allOut[i] == pw) {
//                        allOut[i] = allOut[allOut.length - 1];
//                        allOut = Arrays.copyOf(allOut, allOut.length - 1);
//                        sendMessage(host + "下线了，当前在线人数：" + allOut.length);
//                        break;//提高效率
//                    }
//            }
                //统一处理该客户端下线后的工作
                //1将该客户端的输出流从allOut中删除
                synchronized (allOut) {
                    allOut.remove(pw);
                }
                //2将socket关闭释放资源
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        /**
         * 将给定的消息发送给所有客户端
         *
         * @param line
         */
        public void sendMessage(String message) {
            System.out.println(message);

//            synchronized (Server.this) {
//                for (int i = 0; i < allOut.length; i++) {
//                    allOut[i].println(message);//->客户端
//                }
//            }
            //将消息发送给所有客户端
            synchronized (allOut) {
                for (PrintWriter pw : allOut) {
                    pw.println(message);
                }
            }
        }
    }


    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端连接...");
                /*
                ServerSocket一个重要的方法:
                Socket accept()
                接受客户端的连接，该方法是一个阻塞方法，调用会进入阻塞状态(卡住)
                直到一个客户端建立连接，此时该方法会立即返回一个Socket与客户端
                形成对等关系，并利用这个Socket与客户端交互。
                */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了！");
                //启动一个线程来处理与该客户端的交互
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }


}
